// (C) Copyright 2012 Enlightv. All rights reserved.

`timescale 1ns/100ps

// rgb2yuv
// .I_csc_mode(1'b0),
// .I_coe_a1(9'h080), // (13'h0800),
// .I_coe_a2(9'h194), // (13'h194C),
// .I_coe_a3(9'h1EB), // (13'h1EB4),
// .I_coe_a4(9'h080), // (13'h0800),
// .I_coe_b1(9'h04C), // (13'h04C9),
// .I_coe_b2(9'h096), // (13'h0964),
// .I_coe_b3(9'h01D), // (13'h01D3),
// .I_coe_b4(9'h000), // (13'h0000),
// .I_coe_c1(9'h1D4), // (13'h1D4C),
// .I_coe_c2(9'h1AB), // (13'h1AB4),
// .I_coe_c3(9'h080), // (13'h0800),
// .I_coe_c4(9'h080)  // (13'h0800)

// yuv2rgb
// .I_csc_mode(1'b1),
// .I_coe_a1(9'h0b3), // (13'h0b36),
// .I_coe_a2(9'h080), // (13'h0800),
// .I_coe_a3(9'h1ff), // (13'h1ffe),
// .I_coe_a4(9'h1a6), // (13'h1a66),
// .I_coe_b1(9'h1a4), // (13'h1a49),
// .I_coe_b2(9'h080), // (13'h0800),
// .I_coe_b3(9'h1d4), // (13'h1d40),
// .I_coe_b4(9'h043), // (13'h043c),
// .I_coe_c1(9'h000), // (13'h0001),
// .I_coe_c2(9'h080), // (13'h0800),
// .I_coe_c3(9'h0e2), // (13'h0e2d),
// .I_coe_c4(9'h18e)  // (13'h18e9)

module csc_top
#(parameter
    DATA_BW = 12,
    COE_BW = 13,
    BYPASS_MODE = "DEFAULT"
)
(
    // global
    input  wire             I_clk,
    input  wire             I_rst_n,

    // 
    input  wire             I_bypass_csc,

    // input
    input  wire [ DATA_BW - 1: 0] I_chnA_in,
    input  wire [ DATA_BW - 1: 0] I_chnB_in,
    input  wire [ DATA_BW - 1: 0] I_chnC_in,
    input  wire             I_vsync_in,
    input  wire             I_hsync_in,
    input  wire             I_de_in,
    
    // output
    (* KEEP = "TRUE" *) output reg    [ DATA_BW - 1: 0] O_chnA_out,
    (* KEEP = "TRUE" *) output reg    [ DATA_BW - 1: 0] O_chnB_out,
    (* KEEP = "TRUE" *) output reg    [ DATA_BW - 1: 0] O_chnC_out,
    (* KEEP = "TRUE" *) output reg             O_vsync_out,
    (* KEEP = "TRUE" *) output reg             O_hsync_out,
    (* KEEP = "TRUE" *) output reg             O_de_out,

    //
    input  wire             I_csc_mode,
    input  wire [ COE_BW - 1: 0] I_coe_a1,
    input  wire [ COE_BW - 1: 0] I_coe_a2,
    input  wire [ COE_BW - 1: 0] I_coe_a3,
    input  wire [ COE_BW - 1: 0] I_coe_a4,
    input  wire [ COE_BW - 1: 0] I_coe_b1,
    input  wire [ COE_BW - 1: 0] I_coe_b2,
    input  wire [ COE_BW - 1: 0] I_coe_b3,
    input  wire [ COE_BW - 1: 0] I_coe_b4,
    input  wire [ COE_BW - 1: 0] I_coe_c1,
    input  wire [ COE_BW - 1: 0] I_coe_c2,
    input  wire [ COE_BW - 1: 0] I_coe_c3,
    input  wire [ COE_BW - 1: 0] I_coe_c4

);

/*******************************************************************************
                                   localparm
*******************************************************************************/
localparam
    PROD_BW = DATA_BW + COE_BW, // 25
    SUM_BW = PROD_BW + 1; // 26

/*******************************************************************************
                                internal signal
*******************************************************************************/
// latch input
(* KEEP = "TRUE" *) reg  vsync_reg;
(* KEEP = "TRUE" *) reg  hsync_reg;
(* KEEP = "TRUE" *) reg  de_reg;
(* KEEP = "TRUE" *) reg  [ DATA_BW - 1: 0] chnA_reg; // +12.0
(* KEEP = "TRUE" *) reg  [ DATA_BW - 1: 0] chnB_reg; // +12.0
(* KEEP = "TRUE" *) reg  [ DATA_BW - 1: 0] chnC_reg; // +12.0

// register input
(* KEEP = "TRUE" *) reg  vsync_in;
(* KEEP = "TRUE" *) reg  hsync_in;
(* KEEP = "TRUE" *) reg  de_in;
(* KEEP = "TRUE" *) reg  [ DATA_BW - 1: 0] chnA_in; // +12.0
(* KEEP = "TRUE" *) reg  [ DATA_BW - 1: 0] chnB_in; // +12.0
(* KEEP = "TRUE" *) reg  [ DATA_BW - 1: 0] chnC_in; // +12.0

// multiplication
reg  mult_vsync,mult_hsync,mult_de;
reg  [ DATA_BW * 3 - 1: 0] mult_bypass_data;
wire signed [ PROD_BW - 1: 0] am1,am2,am3,bm1,bm2,bm3,cm1,cm2,cm3; // 13.12
reg  signed [ PROD_BW - 1: 0] am1_reg,am2_reg,am3_reg; // 13.12
reg  signed [ PROD_BW - 1: 0] bm1_reg,bm2_reg,bm3_reg; // 13.12
reg  signed [ PROD_BW - 1: 0] cm1_reg,cm2_reg,cm3_reg; // 13.12

// sum
reg  sum_vsync1,sum_vsync,sum_hsync1,sum_hsync,sum_de1,sum_de;
reg  [ DATA_BW * 3 - 1: 0] sum_bypass_data1;
reg  [ DATA_BW * 3 - 1: 0] sum_bypass_data;
reg  signed [ SUM_BW - 1: 0] asum_tmp1,asum_tmp2; // 14.12
reg  signed [ SUM_BW - 1: 0] bsum_tmp1,bsum_tmp2; // 14.12
reg  signed [ SUM_BW - 1: 0] csum_tmp1,csum_tmp2; // 14.12
reg  signed [ SUM_BW - 1: 0] asum,bsum,csum; // 14.12
reg  [ SUM_BW : 0] asum_csc0_pre;
reg  [ SUM_BW : 0] bsum_csc0_pre;
reg  [ SUM_BW : 0] csum_csc0_pre;

// saturate
reg  sat_vsync,sat_hsync,sat_de;
reg  [ DATA_BW * 3 - 1: 0] sat_bypass_data;
wire [ SUM_BW - 3: 0] asum_csc0,bsum_csc0,csum_csc0;
wire [ SUM_BW - 3: 0] asum_sat0,bsum_sat0,csum_sat0;
reg  [ SUM_BW - 3: 0] asum_sat0_reg,bsum_sat0_reg,csum_sat0_reg; // +12.12

// result
reg  vsync_out,hsync_out,de_out;
reg  [ DATA_BW * 3 - 1: 0] bypass_data_out;
reg  [ DATA_BW - 1: 0] chnA_out,chnB_out,chnC_out; // +12.0

/*******************************************************************************
                                  module body
*******************************************************************************/
//------------------------------------------------------------------------------
// pipeline diagram
//00-01-02-03-04-05-06-07-08-09-10
// 1  2  3                         I_chnA~C_in 
//    1  2  3                      chnA~C_in      
//       1  2  3                   am1~3_reg,bm1~3_reg,cm1~3_reg
//          1  2  3                a~csum_tmp1,a~csum_tmp2
//             1  2  3             a~csum
//                1  2  3          a~csum_sat0_reg
//                   1  2  3       chnA~C_out
//                      1  2  3    O_chnA~C_out
//------------------------------------------------------------------------------
// latch input
always @(posedge I_clk)
    begin
    vsync_reg <= I_vsync_in;
    hsync_reg <= I_hsync_in;
    de_reg <= I_de_in;
    chnA_reg <= I_chnA_in;
    chnB_reg <= I_chnB_in;
    chnC_reg <= I_chnC_in;
    end

// register input (1 cycle)
always @(posedge I_clk)
    begin
    vsync_in <= vsync_reg;
    hsync_in <= hsync_reg;
    de_in <= de_reg;
    chnA_in <= chnA_reg;
    chnB_in <= chnB_reg;
    chnC_in <= chnC_reg;
    end

// multiplication (1 cycle)
assign am1 = $signed({1'b0,chnA_in}) * $signed(I_coe_a1); // 13.0*1.12=14.12-->13.12
assign am2 = $signed({1'b0,chnB_in}) * $signed(I_coe_a2);
assign am3 = $signed({1'b0,chnC_in}) * $signed(I_coe_a3);
assign bm1 = $signed({1'b0,chnA_in}) * $signed(I_coe_b1);
assign bm2 = $signed({1'b0,chnB_in}) * $signed(I_coe_b2);
assign bm3 = $signed({1'b0,chnC_in}) * $signed(I_coe_b3);
assign cm1 = $signed({1'b0,chnA_in}) * $signed(I_coe_c1);
assign cm2 = $signed({1'b0,chnB_in}) * $signed(I_coe_c2);
assign cm3 = $signed({1'b0,chnC_in}) * $signed(I_coe_c3);

always @(posedge I_clk)
    begin
    mult_vsync <= vsync_in;
    mult_hsync <= hsync_in;
    mult_de <= de_in;
    mult_bypass_data <= {chnA_in,chnB_in,chnC_in};
    am1_reg <= am1;
    am2_reg <= am2;
    am3_reg <= am3;
    bm1_reg <= bm1;
    bm2_reg <= bm2;
    bm3_reg <= bm3;
    cm1_reg <= cm1;
    cm2_reg <= cm2;
    cm3_reg <= cm3;
    end

// sum (2 cycle)
always @(posedge I_clk)
    begin
    sum_vsync1 <= mult_vsync;
    sum_hsync1 <= mult_hsync;
    sum_de1 <= mult_de;
    sum_bypass_data1 <= mult_bypass_data;
    asum_tmp1 <= $signed(am1_reg) + $signed(am2_reg);
    asum_tmp2 <= $signed(am3_reg) + $signed({I_coe_a4,{(DATA_BW){1'b0}}});
    bsum_tmp1 <= $signed(bm1_reg) + $signed(bm2_reg);
    bsum_tmp2 <= $signed(bm3_reg) + $signed({I_coe_b4,{(DATA_BW){1'b0}}});
    csum_tmp1 <= $signed(cm1_reg) + $signed(cm2_reg);
    csum_tmp2 <= $signed(cm3_reg) + $signed({I_coe_c4,{(DATA_BW){1'b0}}});
    end

always @(posedge I_clk)
    begin
    sum_vsync <= sum_vsync1;
    sum_hsync <= sum_hsync1;
    sum_de <= sum_de1;
    sum_bypass_data <= sum_bypass_data1;
    asum <= $signed(asum_tmp1) + $signed(asum_tmp2);
    bsum <= $signed(bsum_tmp1) + $signed(bsum_tmp2);
    csum <= $signed(csum_tmp1) + $signed(csum_tmp2);
    end

always @(*)
    if (I_csc_mode == 1'b1)
        begin
        asum_csc0_pre = {asum,1'b0};
        bsum_csc0_pre = {bsum,1'b0};
        csum_csc0_pre = {csum,1'b0};
        end
    else
        begin
        asum_csc0_pre = asum;
        bsum_csc0_pre = bsum;
        csum_csc0_pre = csum;
        end

// saturate sum to logic 1-delta when csc = 0 or 1
assign asum_csc0 = asum_csc0_pre[SUM_BW-2] ? {(SUM_BW-2){1'b1}} : asum_csc0_pre[SUM_BW-3:0];
assign bsum_csc0 = bsum_csc0_pre[SUM_BW-2] ? {(SUM_BW-2){1'b1}} : bsum_csc0_pre[SUM_BW-3:0];
assign csum_csc0 = csum_csc0_pre[SUM_BW-2] ? {(SUM_BW-2){1'b1}} : csum_csc0_pre[SUM_BW-3:0];

assign asum_sat0 = asum_csc0_pre[SUM_BW-1] ? 0 : asum_csc0;
assign bsum_sat0 = bsum_csc0_pre[SUM_BW-1] ? 0 : bsum_csc0;
assign csum_sat0 = csum_csc0_pre[SUM_BW-1] ? 0 : csum_csc0;

always @(posedge I_clk)
    begin
    sat_vsync <= sum_vsync;
    sat_hsync <= sum_hsync;
    sat_de <= sum_de;
    sat_bypass_data <= sum_bypass_data;
    asum_sat0_reg <= asum_sat0;
    bsum_sat0_reg <= bsum_sat0;
    csum_sat0_reg <= csum_sat0;
    end

// result
always @(posedge I_clk)
    begin
    vsync_out <= sat_vsync;
    hsync_out <= sat_hsync;
    de_out <= sat_de;
    bypass_data_out <= sat_bypass_data;
    chnA_out <= (asum_sat0_reg[SUM_BW-3:COE_BW-1] == {DATA_BW{1'b1}}) ?
                    {DATA_BW{1'b1}} :
                    (asum_sat0_reg[SUM_BW-3:COE_BW-1] + asum_sat0_reg[COE_BW-2]);
    chnB_out <= (bsum_sat0_reg[SUM_BW-3:COE_BW-1] == {DATA_BW{1'b1}}) ?
                    {DATA_BW{1'b1}} :
                    (bsum_sat0_reg[SUM_BW-3:COE_BW-1] + bsum_sat0_reg[COE_BW-2]);
    chnC_out <= (csum_sat0_reg[SUM_BW-3:COE_BW-1] == {DATA_BW{1'b1}}) ?
                    {DATA_BW{1'b1}} :
                    (csum_sat0_reg[SUM_BW-3:COE_BW-1] + csum_sat0_reg[COE_BW-2]);
    end

// output
always @(posedge I_clk)
    if (I_bypass_csc)
        begin
        O_vsync_out <= vsync_out;
        O_hsync_out <= hsync_out;
        O_de_out <= de_out;
        if (BYPASS_MODE == "DEFAULT")
            begin
            O_chnA_out <= bypass_data_out[DATA_BW-1:0];
            O_chnB_out <= bypass_data_out[DATA_BW*3-1:DATA_BW*2];
            O_chnC_out <= bypass_data_out[DATA_BW*2-1:DATA_BW];
            end
        else if (BYPASS_MODE == "DIRECT")
            begin
            O_chnA_out <= bypass_data_out[DATA_BW*3-1:DATA_BW*2];
            O_chnB_out <= bypass_data_out[DATA_BW*2-1:DATA_BW];
            O_chnC_out <= bypass_data_out[DATA_BW-1:0];
            end
        end
    else
        begin
        O_vsync_out <= vsync_out;
        O_hsync_out <= hsync_out;
        O_de_out <= de_out;
        O_chnA_out <= chnA_out;
        O_chnB_out <= chnB_out;
        O_chnC_out <= chnC_out;
        end

endmodule

`default_nettype wire
